package com.dingwen.spsest.config;

import com.dingwen.spsest.filter.VerifyCodeFilter;
import com.dingwen.spsest.handler.MyAuthenticationFailureHandler;
import com.dingwen.spsest.handler.MyAuthenticationSuccessHandler;
import com.dingwen.spsest.mapper.MenuMapper;
import com.dingwen.spsest.mapper.RoleMapper;
import com.dingwen.spsest.mapper.UserMapper;
import com.dingwen.spsest.service.impl.UserDetailsServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

import javax.sql.DataSource;

/**
 * security 配置类
 *
 * @author dingwen
 * 2021.05.17 15:31
 */

@Configuration
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true) // 开启注解
@EnableWebSecurity //开启csrf
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    /**
     * 用户映射器
     */
    private final UserMapper userMapper;
    /**
     * 角色映射器
     */
    private final RoleMapper roleMapper;
    /**
     * 菜单映射器
     */
    private final MenuMapper menuMapper;


    /**
     * 验证代码过滤器
     */
    private final VerifyCodeFilter verifyCodeFilter;


    /**
     * 我的身份验证失败处理程序
     */
    private final MyAuthenticationFailureHandler myAuthenticationFailureHandler;


    /**
     * 我的身份验证成功处理程序
     */
    private final MyAuthenticationSuccessHandler myAuthenticationSuccessHandler;
    private final DataSource dataSource;


    @Autowired
    SecurityConfig(UserMapper userMapper,
                   RoleMapper roleMapper,
                   VerifyCodeFilter verifyCodeFilter,
                   MyAuthenticationFailureHandler myAuthenticationFailureHandler,
                   MyAuthenticationSuccessHandler myAuthenticationSuccessHandler,
                   DataSource dataSource,
                   MenuMapper menuMapper) {
        this.userMapper = userMapper;
        this.roleMapper = roleMapper;
        this.menuMapper = menuMapper;
        this.verifyCodeFilter = verifyCodeFilter;
        this.myAuthenticationFailureHandler = myAuthenticationFailureHandler;
        this.dataSource = dataSource;
        this.myAuthenticationSuccessHandler = myAuthenticationSuccessHandler;
    }

    /**
     * 密码编码器
     *
     * @return {@link BCryptPasswordEncoder}
     */
    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder() {
        return new BCryptPasswordEncoder(10); // strength: 加密强度[-1,31]
    }

    /**
     * 用户详细信息服务
     *
     * @return {@link UserDetailsService}
     */
    @Bean
    public UserDetailsService userDetailsService() {

        /*// 内存用户测试
        InMemoryUserDetailsManager inMemoryUserDetailsManager = new InMemoryUserDetailsManager();
        // 逗号分隔的字符串到授权列表
        List<GrantedAuthority> authorityList = Authority    Utils.commaSeparatedStringToAuthorityList("admin,user");
        // 内存用户
        inMemoryUserDetailsManager.createUser(User.withUsername("dingwen")
                .password(bCryptPasswordEncoder()
                        .encode("123456"))
                .authorities(authorityList)
                .build());
        return inMemoryUserDetailsManager;*/

        //数据库用户查询
        return new UserDetailsServiceImpl(userMapper, roleMapper, menuMapper);
    }


    /**
     * 基于数据库的记住我功能实现
     *
     * @return {@link PersistentTokenRepository}
     */
    @Bean
    public PersistentTokenRepository persistentTokenRepository() {
        JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
        // 设置数据源
        jdbcTokenRepository.setDataSource(dataSource);
        // 自动创建表,第一次执行会创建，以后要执行就要删除掉！
//        jdbcTokenRepository.setCreateTableOnStartup(true);
        return jdbcTokenRepository;

    }


    /**
     * HttpSecurity 配置
     *
     * @param http http
     * @throws Exception 异常
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {

        // 退出登录
        http.logout()
                .logoutUrl("/logout")
                .logoutSuccessUrl("/index")
                .logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
                .permitAll();

        // 基于数据库的记住我
        http.rememberMe()
                .tokenValiditySeconds(60 * 10) // 设置有效期，单位秒
                .tokenRepository(persistentTokenRepository())
                .userDetailsService(userDetailsService());

        // 添加验证码过滤
        http.addFilterBefore(verifyCodeFilter, UsernamePasswordAuthenticationFilter.class);
        /*http.formLogin() //表单登录
                .and()
                .authorizeRequests() // 认证配置
                .anyRequest() // 任何请求
                .authenticated(); // 都需要认证通过*/
        http
                .formLogin() // 配置登录
                .loginPage("/index") // 登录页面的请求url(跳转页面)
                .loginProcessingUrl("/login") // 哪个请求是登录页面的url(提交表单)
                .successForwardUrl("/success") // 登录成功之后需要跳转的url
//                .usernameParameter("自定义用户名变量名称")
//                .passwordParameter("自定义密码变量名称")
//                .failureUrl("/fail")// 登录失败之后需要跳转的url
                .successHandler(myAuthenticationSuccessHandler)
                .failureHandler(myAuthenticationFailureHandler); //登录失败处理


        http
                //.csrf() //跨站请求伪造
                //.disable()// 关闭跨站请求伪造
                .authorizeRequests() // 认证配置
                .antMatchers("/index/** ", "/code/**", "/static/**")
                .permitAll() // 指定url放行
                .antMatchers("/find").hasAuthority("menu:user")
                .antMatchers("/test1").hasAnyRole("普通用户", "管理员")
                .antMatchers("/findAll").hasRole("管理员")
                .antMatchers("/test2").hasAnyAuthority("menu:user", "menu:test")
                .anyRequest() // 其他请求
                .authenticated();//都需要认证通过


        // 自定义403页面

        http.exceptionHandling().accessDeniedPage("/forbidden");

    }


    /**
     * AuthenticationManagerBuilder配置
     * 自定义 userDetailsService时不设置会出现 “No AuthenticationProvider found” 错误
     *
     * @param auth 身份验证
     * @throws Exception 异常
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService()).passwordEncoder(bCryptPasswordEncoder());
    }

}
